﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Reflection;

namespace Postnuclear.Dependency.Tree
  {
  /// <summary>
  /// The element of dependencies tree. It holds the information about the dependency from one member(field or property).
  /// </summary>
  /// <typeparam name="THolder">The type of the holder.</typeparam>
  /// <typeparam name="TMember">The type of the value.</typeparam>
  /// <remarks>
  /// To track the member value its holder should implement one of 3 approaches for providing the notifications about changes of the property value:
  /// - Specific event per each property which should be tracked.
  ///   Event name should be 'PropertyName'Changed.
  ///   Event type should implement general event pattern where the first parameter is the sender and the second one is derived from <see cref="EventArgs"/>.
  /// - Implement <see cref="INotifyPropertyChanged"/>.
  ///   In this case the <see cref="PropertyChangedEventArgs.PropertyName"/> property is used to check if the tracked property was changed or not.
  /// - Implement <see cref="INotifyCollectionChanged" />.
  ///   In this case property value is reevaluated each time the even is raised.
  /// </remarks>
  internal sealed class MemberDependencyElement<THolder, TMember>
    : NotifyValueChanged, IDependencyElement<THolder, TMember>
    where THolder : class
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="MemberDependencyElement{THolder, TMember}"/> class.
    /// </summary>
    /// <param name="member">The member information.</param>
    public MemberDependencyElement(MemberInfo member)
      {
      Contract.Requires<ArgumentNullException>(member != null);
      Contract.Requires<ArgumentException>(member.DeclaringType.IsAssignableFrom(typeof(THolder)));

      EventAdapter = new EventAdapter<THolder>(member);

      // Create a delegate which gets the property value instead of usage reflection each time to get the value of the property.
      Expression<Func<THolder>> holderExpression = () => Holder;
      MemberExpression propertyExpression = Expression.MakeMemberAccess(holderExpression.Body, member);
      PropertyValue = Expression.Lambda<Func<TMember>>(propertyExpression).Compile();
      }

    // Properties

    /// <summary>
    /// Gets the dependency element value.
    /// </summary>
    [DebuggerNonUserCode]
    public TMember Value
      {
      get
        {
        try
          {
          return PropertyValue();
          }
        catch (Exception)
          {
          // if there were problems during getting the property 
          // the property value is set to undefined
          return default(TMember);
          }
        }
      }

    // Methods

    /// <summary>
    /// Updates the dependency holder instance.
    /// </summary>
    /// <param name="holder">The holder.</param>
    public void UpdateHolderValue(THolder holder)
      {
      Holder = holder;
      EventAdapter.UpdateHolderValue(Holder);
      }

    #endregion

    #region Protected

    // Methods

    /// <summary>
    /// Called when when activation state is changed.
    /// </summary>
    protected override void OnIsActiveChanged()
      {
      if (IsActive)
        {
        EventAdapter.ValueChanged += RaiseValueChanged;
        }
      else
        {
        EventAdapter.ValueChanged -= RaiseValueChanged;
        }
      }

    #endregion

    #region Private

    // Properties

    private WeakReference<THolder> Holder
      {
      get;
      set;
      }

    // Fields

    private readonly IDependencyTreeElement<THolder> EventAdapter;
    private readonly Func<TMember> PropertyValue;

    #endregion
    }
  }